/*
 * 文件名：SignatureUtil.java
 * 版权：Copyright by 联通系统集成有限公司
 * 描述：
 * 修改人：焦凯旋
 * 修改时间：2018年9月19日
 * 跟踪单号：
 * 修改单号：
 * 修改内容：
 */

package com.bicycle.common.helper;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;

public class SignatureHelper {

	private static final Logger LOGGER = LoggerFactory.getLogger(SignatureHelper.class);

	private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

	private static final Random RANDOM = new SecureRandom();

	public enum SignType {
		MD5,
		HMACSHA256
	}

	public static final String FIELD_SIGN = "sign";
	public static final String FIELD_SIGN_TYPE = "sign_type";

	/**
	 * 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
	 *
	 * @param data Map类型数据
	 * @param key API密钥
	 * @return 签名是否正确
	 * @throws Exception
	 */
	public static boolean isSignatureValid(Map<String, Object> data, String key) throws Exception {
		return isSignatureValid(data, key, SignType.MD5);
	}

	/**
	 * 判断签名是否正确，必须包含sign字段，否则返回false。
	 *
	 * @param data Map类型数据
	 * @param key API密钥
	 * @param signType 签名方式
	 * @return 签名是否正确
	 * @throws Exception
	 */
	public static boolean isSignatureValid(Map<String, Object> data, String key, SignType signType) throws Exception {
		if (!data.containsKey(FIELD_SIGN)) {
			return false;
		}
		String sign = (String) data.get(FIELD_SIGN);
		return generateSignature(data, key, signType).equals(sign);
	}

	/**
	 * 生成签名
	 *
	 * @param data 待签名数据
	 * @param key API密钥
	 * @return 签名
	 */
	public static String generateSignature(Map<String, Object> data, String key) throws Exception {
		return generateSignature(data, key, SignType.MD5);
	}

	/**
	 * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
	 *
	 * @param data 待签名数据
	 * @param key API密钥
	 * @param signType 签名方式
	 * @return 签名
	 */
	public static String generateSignature(Map<String, Object> data, String key, SignType signType) throws Exception {
		data = sortJsonValue(data);
		Set<String> keySet = data.keySet();
		String[] keyArray = keySet.toArray(new String[keySet.size()]);
		Arrays.sort(keyArray);
		StringBuilder sb = new StringBuilder();
		for (String k : keyArray) {
			if (k.equals(FIELD_SIGN)) {
				continue;
			}
			if (data.get(k) == null) {
				continue;
			}
			String value = String.valueOf(data.get(k));
			if (value.length() > 0) // 参数值为空，则不参与签名
				sb.append(k).append("=").append(value.trim()).append("&");
		}
		sb.append("key=").append(key);

		if (SignType.MD5.equals(signType)) {
			LOGGER.info("preSignature:{},signature:{}", sb.toString(), MD5(sb.toString()).toUpperCase());
			return MD5(sb.toString()).toUpperCase();
		} else if (SignType.HMACSHA256.equals(signType)) {
			LOGGER.info("preSignature:{},signature:{}", sb.toString(), HMACSHA256(sb.toString(), key));
			return HMACSHA256(sb.toString(), key);
		} else {
			throw new Exception(String.format("Invalid sign_type: %s", signType));
		}
	}

	/**
	 * 生产签名加密串
	 *
	 * @param data 待签名数据
	 * @return 签名
	 */
	public static String generatePreSignature(final Map<String, String> data) {
		Set<String> keySet = data.keySet();
		String[] keyArray = keySet.toArray(new String[keySet.size()]);
		Arrays.sort(keyArray);
		StringBuilder sb = new StringBuilder();
		for (String k : keyArray) {
			if (k.equals(FIELD_SIGN)) {
				continue;
			}
			if (data.get(k) == null) {
				continue;
			}
			if (data.get(k).trim().length() > 0) // 参数值为空，则不参与签名
				sb.append(k).append("=").append(data.get(k).trim()).append("&");
		}
		return sb.toString().substring(0, sb.toString().lastIndexOf("&"));
	}

	/**
	 * 生成 MD5
	 *
	 * @param data 待处理数据
	 * @return MD5结果
	 */
	public static String MD5(String data) throws Exception {
		MessageDigest md = MessageDigest.getInstance("MD5");
		byte[] array = md.digest(data.getBytes("UTF-8"));
		StringBuilder sb = new StringBuilder();
		for (byte item : array) {
			sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * 生成 HMACSHA256
	 * 
	 * @param data 待处理数据
	 * @param key 密钥
	 * @return 加密结果
	 * @throws Exception
	 */
	public static String HMACSHA256(String data, String key) throws Exception {
		Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
		SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
		sha256_HMAC.init(secret_key);
		byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
		StringBuilder sb = new StringBuilder();
		for (byte item : array) {
			sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * 获取当前时间戳，单位秒
	 * 
	 * @return
	 */
	public static long getCurrentTimestamp() {
		return System.currentTimeMillis() / 1000;
	}

	/**
	 * 获取当前时间戳，单位毫秒
	 * 
	 * @return
	 */
	public static long getCurrentTimestampMs() {
		return System.currentTimeMillis();
	}

	/**
	 * 判断时间戳是否有效
	 * 
	 * @param timestamp 时间戳，单位秒
	 * @param validTime 有效时间 ，单位秒
	 * @return
	 * @see
	 */
	public static boolean isTimestampValid(long timestamp, long validTime) {
		return getCurrentTimestamp() - validTime > timestamp ? false : true;
	}

	/**
	 * 获取随机字符串 Nonce Str
	 *
	 * @return String 随机字符串
	 */
	public static String generateNonceStr() {
		char[] nonceChars = new char[32];
		for (int index = 0; index < nonceChars.length; ++index) {
			nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
		}
		return new String(nonceChars);
	}

	/**
	 * 判断字符串是否为json
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isJsonStr(String str) {
		try {
			JSON.parse(str);
		} catch (Exception e) {
			return false;
		}
		return true;
	}

	/**
	 * 排序value为json数据中的key
	 * 
	 * @param parameters
	 * @return
	 */
	public static Map<String, Object> sortJsonValue(Map<String, Object> parameters) {
		parameters.forEach((key, value) -> {
			if (isJsonStr(String.valueOf(value))) {
				if (JSON.parse(String.valueOf(value)) instanceof JSONArray) {

					JSONArray jsonArray = JSON.parseArray(String.valueOf(value));
					List<Map<String, Object>> maps = new ArrayList<>();
					for (int i = 0; i < jsonArray.size(); i++) {
						if (jsonArray.get(i) instanceof JSONObject) {
							JSONObject object = jsonArray.getJSONObject(i);
							Map<String, Object> parameter = new TreeMap<String, Object>();
							for (String string : object.keySet()) {
								parameter.put(string, object.get(string));
							}
							parameter = sortJsonValue(parameter);
							maps.add(parameter);
						} else if (jsonArray.get(i) instanceof JSONArray) {
							JSONObject object = jsonArray.getJSONObject(i);
							Map<String, Object> parameter = new TreeMap<String, Object>();
							for (String string : object.keySet()) {
								parameter.put(string, object.get(string));
							}
							parameter = sortJsonValue(parameter);
							maps.add(parameter);
						}
					}
					if (maps.isEmpty()) {
						parameters.put(key, value);
					} else {
						parameters.put(key, JSON.toJSON(maps));
					}
				} else if (JSON.parse(String.valueOf(value)) instanceof JSONObject) {
					JSONObject object = JSON.parseObject(String.valueOf(value));
					Map<String, Object> parameter = new TreeMap<String, Object>();
					for (String string : object.keySet()) {
						parameter.put(string, object.getString(string));
					}
					parameter = sortJsonValue(parameter);
					parameters.put(key, JSON.toJSONString(parameter));
				}
			}
		});
		return parameters;
	}

	public static void main(String[] args) throws Exception {

		String json = "{\"templateName\":\"啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊111\",\"templateInfoList\":[{\"classifyId\":14,\"classifyName\":\"类别1\",\"itemList\":[{\"itemsId\":100,\"classifyId\":14,\"itemsName\":\"121232\",\"itemsDesc\":\"333\",\"scoreRule\":\"323232\",\"hasAttachment\":0,\"hasRoadTransport\":1,\"hasTraffic\":0,\"hasCityManage\":0,\"hasAreaGov\":0,\"hasDel\":0,\"createName\":\"罗湖区普通用户\",\"createBy\":50,\"createTime\":\"2019-07-30 09:47:42\",\"updateName\":null,\"updateBy\":null,\"updateTime\":\"2019-07-30 09:47:41\",\"classifyName\":\"类别1\",\"standardScore\":100,\"classifyScore\":100}]}],\"sign\":\"0BEC66A0CA80BC9028FE3285B10CBEC9\"}";

		// String json =
		// "{\"name\":\"BeJson\",\"url\":\"http://www.bejson.com\",\"page\":88,\"isNonProfit\":true,\"address\":{\"street\":\"科技园路.\",\"city\":\"江苏苏州\",\"country\":\"中国\"},\"links\":[{\"name\":\"Google\",\"url\":\"http://www.google.com\"},{\"name\":\"Baidu\",\"url\":\"http://www.baidu.com\"},{\"name\":\"SoSo\",\"url\":\"http://www.SoSo.com\"}]}";
		json = "{\"code\":\"8888\",\"reportType\":[\"1\",\"2\",\"3\"],\"orderType\":\"1\",\"reportContent\":\"hello\",\"accessToken\":\"\",\"districtId\":3,\"lng\":113.93694993069373,\"lat\":22.53901657788418,\"detailAddress\":\"桃园路2号22层\",\"enterpriseNos\":[\"075501\",\"075502\",\"075505\",\"075506\",\"075510\"],\"reportMediaId\":[\"1237378768e7q8e7r8qwesafdasdfasdfaxss111\",\"1237378768e7q8e7r8qwesafdasdfasdfaxss111\"],\"sign\":\"307A4810FBEDD7756E802F5A2F90DE45\"}";
		json = "{\"templateName\":\"123\",\"templateInfoList\":[{\"classifyId\":14,\"classifyName\":\"类别1\",\"itemList\":[{\"classifyId\":14,\"classifyName\":\"类别1\",\"classifyScore\":50,\"createBy\":50,\"createName\":\"罗湖区普通用户\",\"createTime\":\"2019-07-30 09:47:42\",\"hasAreaGov\":0,\"hasAttachment\":0,\"hasCityManage\":0,\"hasDel\":0,\"hasRoadTransport\":1,\"hasTraffic\":0,\"itemsDesc\":\"333\",\"itemsId\":100,\"itemsName\":\"121232\",\"scoreRule\":\"323232\",\"standardScore\":50,\"updateTime\":\"2019-07-30 09:47:41\"}]},{\"classifyId\":15,\"classifyName\":\"类别2\",\"itemList\":[{\"classifyId\":15,\"classifyName\":\"类别2\",\"classifyScore\":20,\"createBy\":18,\"createName\":\"test\",\"createTime\":\"2019-07-06 02:52:28\",\"hasAreaGov\":0,\"hasAttachment\":0,\"hasCityManage\":1,\"hasDel\":0,\"hasRoadTransport\":0,\"hasTraffic\":0,\"itemsDesc\":\"指标3\",\"itemsId\":55,\"itemsName\":\"指标3\",\"scoreRule\":\"指标3\",\"standardScore\":20,\"updateBy\":18,\"updateName\":\"市交通局管理员\",\"updateTime\":\"2019-07-18 07:23:19\"}]},{\"classifyId\":16,\"classifyName\":\"类别3\",\"itemList\":[{\"classifyId\":16,\"classifyName\":\"类别3\",\"classifyScore\":30,\"createBy\":18,\"createName\":\"市交通局管理员\",\"createTime\":\"2019-07-10 02:32:48\",\"hasAreaGov\":1,\"hasAttachment\":0,\"hasCityManage\":1,\"hasDel\":0,\"hasRoadTransport\":1,\"hasTraffic\":1,\"itemsDesc\":\"指标5\",\"itemsId\":80,\"itemsName\":\"指标5\",\"scoreRule\":\"指标5\",\"standardScore\":30,\"updateBy\":18,\"updateName\":\"市交通局管理员\",\"updateTime\":\"2019-07-16 06:05:54\"}]}],\"sign\":\"9254AA7CC83598FB41BCB405E2BC1023\"}";
		System.out.println(json);
		Map<String, Object> parameters = new HashMap<String, Object>();
		JSONObject jsonObject = JSON.parseObject(json);
		for (String key : jsonObject.keySet()) {
			parameters.put(key, jsonObject.getString(key));
		}

		parameters = sortJsonValue(parameters);
		// Map<String, String> parameter2 = new TreeMap<String, String>();
		// parameters.forEach((key, value) -> {
		// System.out.println(key + "=" + value);
		//// parameter2.put(key, value);
		// if (isJsonStr(value)) {
		// if (JSON.parse(value) instanceof JSONArray) {
		// List<Map<String, String>> maps = new ArrayList<>();
		// JSONArray jsonArray = JSON.parseArray(value);
		// for (int i = 0; i < jsonArray.size(); i++) {
		// JSONObject object = jsonArray.getJSONObject(i);
		// Map<String, String> parameter = new TreeMap<String, String>();
		// for (String string : object.keySet()) {
		// parameter.put(string, object.getString(string));
		// }
		// maps.add(parameter);
		// }
		// parameters.put(key, JSON.toJSONString(maps));
		// }
		// }
		// });
		parameters.forEach((key, value) -> {
			System.out.println(key + "=" + value);
		});

		SignatureHelper.isSignatureValid(parameters, "a8953039d236276bc3a8344009f4a485");

	}

}
